package client

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"gitee.com/mqyqingkong/submit-and-listen/cache"
	"github.com/gin-gonic/gin"
	uuid "github.com/satori/go.uuid"
	"github.com/sirupsen/logrus"
)

type Client struct {
	reqListenerMap   *cache.TimeOutCache
	logger           *logrus.Logger
	getServerAddress GetServerAddress
	getClientAddress GetClientAddress
	listenUri        string // /listen
	engine                             *gin.Engine
}

type Option func(c *Client)

func WithLogger(logger *logrus.Logger) Option {
	return func(c *Client) {
		c.logger = logger
	}
}

func WithEngine(engine *gin.Engine) Option {
	return func(c *Client) {
		c.engine = engine
	}
}

func WithListenUri(listenUri string) Option {
	return func(c *Client) {
		c.listenUri = listenUri
	}
}

func WithGetServerAddress(getServerAddress GetServerAddress) Option {
	return func(c *Client) {
		c.getServerAddress = getServerAddress
	}
}

func WithGetClientAddress(getClientAddress GetClientAddress) Option {
	return func(c *Client) {
		c.getClientAddress = getClientAddress
	}
}

//可用于负载均衡
type GetServerAddress func() string // http://ip:port
// 
type GetClientAddress func() string // http://ip:port

func NewClient(options ...Option) *Client {
	c := &Client{
		reqListenerMap: cache.NewTimeOutCache(1024),
		logger:         &logrus.Logger{Out: os.Stderr, Formatter: new(logrus.TextFormatter), Hooks: make(logrus.LevelHooks), Level: logrus.InfoLevel},
		getServerAddress: func() string {
			return "http://127.0.0.1:8181"
		},
		getClientAddress: func() string {
			return "http://127.0.0.1:8180"
		},
		listenUri:     "/client",
	}
	for _, option := range options {
		option(c)
	}
	return c
}

type listenerReq struct {
	listener  Listener
	submitUri string
	timeout time.Time
}
func (r listenerReq) TimeOut() time.Time {
	return r.timeout
}

func newListenerReq(listener  Listener, submitUri string) *listenerReq {
	r := &listenerReq{
		listener:  listener,
		submitUri: submitUri,
		timeout:   time.Now().Add(15 * time.Minute),
	}
	return r
}

func (c *Client) Submit(submitUri string, params map[string]string, l Listener) error {
	reqId := uuid.NewV4().String()
	var vals = url.Values{}
	vals["reqId"] = []string{reqId}
	if l != nil {
		vals["clientUrl"] = []string{c.getClientAddress() + c.listenUri}
	}
	for k, v := range params {
		vals[k] = []string{v}
	}

	c.reqListenerMap.Set(reqId, newListenerReq(l, submitUri))
	
	c.logger.Debugf("begin submit task, reqId:%s, reqUri:%s, params:%s", reqId, submitUri, params)
	resp, err := http.PostForm(c.getServerAddress()+submitUri, vals)
	if err != nil {
		c.reqListenerMap.Delete(reqId)
		c.logger.Errorf("submit task error, reqId:%s, reqUri:%s, error:%v", reqId, submitUri, err)
		return err
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)

	var submitResult = &SubmitResult{}
	err = json.Unmarshal(body, submitResult)
	if err != nil {
		c.reqListenerMap.Delete(reqId)
		c.logger.Errorf("submit task error, reqId:%s, reqUri:%s, error:%v", reqId, submitUri, err)
		return err
	}
	c.logger.Debugf("submit task complete, reqId:%s, reqUri:%s, response:%s", reqId, submitUri, submitResult)
	if submitResult.Result == "success" && submitResult.Complete == "true" {
		c.reqListenerMap.Delete(reqId)
		// 提交后，如果执行完毕，直接执行监听逻辑
		l.Listen(reqId, submitUri, submitResult.Response, nil)

	} else if submitResult.Result != "success" {
		c.reqListenerMap.Delete(reqId)
		c.logger.Errorf("submit task response fail, reqId:%s, reqUri:%s, response:%s", reqId, submitUri, string(body))
	}
	return nil
}

type SubmitResult struct {
	Result   string `json:"result"`
	Complete string `json:"complete"`
	Response string `json:"response"`
}

func (c *Client) StartListen() {
	newEngine := false
	r := c.engine
	if r == nil {
		r = gin.Default()
		newEngine = true
	}


	r.POST(c.listenUri, func(ctx *gin.Context) {
		ctx.Request.ParseForm()
		reqId := ctx.Request.FormValue("reqId")
		result := ctx.Request.FormValue("result")
		listenerReqObj, found := c.reqListenerMap.Get(reqId)
		
		if result == "success" && found {
			listenerReq := listenerReqObj.(*listenerReq)
			response := ctx.Request.FormValue("response")
			err := listenerReq.listener.Listen(reqId, listenerReq.submitUri, response, nil)
			if err != nil {
				c.logger.Errorf("listener execute error. reqUri: %s, reqId: %s, response:%s,  err: %v",
					listenerReq.submitUri, reqId, response, err)
				ctx.JSON(200, gin.H{
					"result": "fail",
					"msg":    err.Error(),
				})
			} else {
				c.reqListenerMap.Delete(reqId)
				ctx.JSON(200, gin.H{
					"result": "success",
				})
			}

		} else {
			if found {
				listenerReq := listenerReqObj.(*listenerReq)
				msg := ctx.Request.FormValue("msg")
				c.logger.Errorf("Server executed error. reqUri: %s, reqId: %s, msg: %s", listenerReq.submitUri, reqId, msg)
				err := errors.New(msg)
				err = listenerReq.listener.Listen(reqId, listenerReq.submitUri, "", err)
				if err != nil {
					c.logger.Errorf("listener execute error. reqUri: %s, reqId: %s,  err: %v",
						listenerReq.submitUri, reqId, err)
					ctx.JSON(200, gin.H{
						"result": "fail",
						"msg":    err.Error(),
					})
				} else {
					c.reqListenerMap.Delete(reqId)
				}
			}
			ctx.JSON(200, gin.H{
				"result": "success",
			})
		}
	})
	
	if newEngine {
		clientAddress := c.getClientAddress()
		ind := strings.LastIndex(clientAddress, ":")
		port := clientAddress[ind:]
		r.Run(port)
	}
}
